package my.mvc;

import my.cache.CacheManager;
import my.db.DBManager;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

/**
 * Created by hackway on 2016/5/17.
 */
public class URLMappingFilter implements Filter {

    private ServletContext context;

    private final Logger log = LoggerFactory.getLogger(URLMappingFilter.class);
    public final static String REQUEST_URI = "index";
    private String PATH_PREFIX;
    private final static String VM_EXT = ".vm";
    private final static String VM_INDEX = "/index" + VM_EXT;

    private final static List<String> ignoreURIs = new ArrayList<String>();
    private final static List<String> ignoreExts = new ArrayList<String>();

    private final static Vector<String> vm_cache = new Vector<>();
    /**
     * Called by the web container to indicate to a filter that it is
     * being placed into service.
     * <p>
     * <p>The servlet container calls the init
     * method exactly once after instantiating the filter. The init
     * method must complete successfully before the filter is asked to do any
     * filtering work.
     * <p>
     * <p>The web container cannot place the filter into service if the init
     * method either
     * <ol>
     * <li>Throws a ServletException
     * <li>Does not return within a time period defined by the web container
     * </ol>
     *
     * @param filterConfig
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        this.context = filterConfig.getServletContext();

        //模板存放路径
        this.PATH_PREFIX = filterConfig.getInitParameter("template-path-prefix");
        if(this.PATH_PREFIX == null)
            this.PATH_PREFIX = "/WEB-INF/templates";
        else if(this.PATH_PREFIX.endsWith("/"))
            this.PATH_PREFIX = this.PATH_PREFIX.substring(0, this.PATH_PREFIX.length()-1);

        //某些URL前缀不予处理（例如 /img/***）
        String ignores = filterConfig.getInitParameter("ignore");
        if (ignores!=null && !ignores.isEmpty()) {
            for (String ig: StringUtils.split(ignores,',')) {
                ignoreURIs.add(ig.trim());
            }
        }
        // 某些URL后缀不予处理(例如 jpg,gif等)
        ignores = filterConfig.getInitParameter("ignoreExts");
        if (ignores!=null &&!ignores.isEmpty()) {
            for (String ig:StringUtils.split(ignores,',')) {
                ignoreExts.add(ig.trim());
            }
        }

    }

    /**
     * The <code>doFilter</code> method of the Filter is called by the
     * container each time a request/response pair is passed through the
     * chain due to a client request for a resource at the end of the chain.
     * The FilterChain passed in to this method allows the Filter to pass
     * on the request and response to the next entity in the chain.
     * <p>
     * <p>A typical implementation of this method would follow the following
     * pattern:
     * <ol>
     * <li>Examine the request
     * <li>Optionally wrap the request object with a custom implementation to
     * filter content or headers for input filtering
     * <li>Optionally wrap the response object with a custom implementation to
     * filter content or headers for output filtering
     * <li>
     * <ul>
     * <li><strong>Either</strong> invoke the next entity in the chain
     * using the FilterChain object
     * (<code>chain.doFilter()</code>),
     * <li><strong>or</strong> not pass on the request/response pair to
     * the next entity in the filter chain to
     * block the request processing
     * </ul>
     * <li>Directly set headers on the response after invocation of the
     * next entity in the filter chain.
     * </ol>
     *
     * @param request
     * @param response
     * @param chain
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //自动编码处理

        HttpServletRequest req = (HttpServletRequest)request;
        HttpServletResponse res = (HttpServletResponse)response;
        if ("HEAD".equalsIgnoreCase(req.getMethod())) {
            return;
        }
        RequestContext rc = RequestContext.begin(this.context, req, res);
        String req_uri = rc.uri();
        try {
            //过滤URL前缀
            for (String ignoreURI:ignoreURIs) {
                if (req_uri.startsWith(ignoreURI)) {
                    chain.doFilter(rc.request(), rc.response());
                    req_uri = (String)rc.request().getAttribute(ActionServlet.ACTION_FORWARD);
                    if(StringUtils.isBlank(req_uri))
                        return ;
                }
            }

            //过滤URL后缀
            for (String ignoreExt:ignoreExts) {
                if (req_uri.endsWith(ignoreExt)) {
                    chain.doFilter(rc.request(), rc.response());
                    return;
                }
            }

            rc.request().setAttribute(REQUEST_URI, req_uri);
            //String vm = (req_uri.endsWith(".html") || req_uri.endsWith("htm"))?this.PATH_PREFIX+req_uri:this.PATH_PREFIX+req_uri+VM_EXT;
            log.info(req_uri);
            String[] parts = StringUtils.split(req_uri,'/');
            String vm = getTemplate(parts,parts.length);
            beforeFilter(rc);
            rc.forward(vm);
            afterFilter(rc);
        } finally {
            try {
                DBManager.closeConnection();
            } catch (Exception e) {

            }
            rc.end();
        }
    }


    protected void beforeFilter(RequestContext ctx) {}
    protected void afterFilter(RequestContext ctx) {}

    /**
     * Called by the web container to indicate to a filter that it is being
     * taken out of service.
     * <p>
     * <p>This method is only called once all threads within the filter's
     * doFilter method have exited or after a timeout period has passed.
     * After the web container calls this method, it will not call the
     * doFilter method again on this instance of the filter.
     * <p>
     * <p>This method gives the filter an opportunity to clean up any
     * resources that are being held (for example, memory, file handles,
     * threads) and make sure that any persistent state is synchronized
     * with the filter's current state in memory.
     */
    @Override
    public void destroy() {
        CacheManager.close();
        DBManager.closeDataSource();
    }

    private String getTemplate(String[] paths, int idx_base) {
        StringBuilder vm = new StringBuilder(this.PATH_PREFIX);
        if (idx_base == 0) {
            return vm.toString()+VM_INDEX; //  /index.vm
        }
        for (int i=0;i<idx_base;i++) {
            vm.append('/').append(paths[i]);
        }
        String vms = vm.toString();
        String the_path = vms;
        if (isVmExist(the_path+VM_EXT))  //  parent.vm
            return the_path + VM_EXT;
        the_path += VM_INDEX;  //  parent/index.vm
        if (isVmExist(the_path))
            return the_path;
        return getTemplate(paths, idx_base-1); //递归查找父级目录
    }

    /**
     * 判断某个页面是否存在，如果存在则缓存此结果
     * @param path
     * @return
     */
    private boolean isVmExist(String path){
        if(vm_cache.contains(path))
            return true;
        boolean isVM = false;
        if(context != null){
            String cpath = context.getRealPath(path);
            if(cpath != null){
                File testFile = new File(cpath);
                isVM = testFile.exists() && testFile.isFile();
                if(isVM)
                    vm_cache.add(path);
            }
        }
        return isVM;
    }
}
